package day17;

import org.junit.Test;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;

/*本类用于测试反射*/
public class Test1Reflect {
    //1.创建程序的入口函数main()---不用!!!
    /* 单元测试方法 : 是Java测试的最小范围,使用灵活,推荐程度:5颗星
       语法要求 : @Test + void + 没有参数 + public
       注意:使用时需要导包:Add JUnit4 to ClassPath
    * */
    /*单元测试1:用来测试获取指定类的字节码对象*/
    @Test
    public void getClazz() throws Exception {
        //1.方式1 : 通过类的全路径名[包名+类名]获取对应的字节码对象
        Class<?> student1 = Class.forName("cn.tedu.reflection.Student");
        //2.方式2 : 直接类名.class获取对应的字节码对象
        Class<?> student2 = Student.class;
        //3.方式3: 通过类的对象来获取,注意,此处创建的是匿名对象,匿名对象没有名字
        Class<?> student3 = new Student().getClass();
        System.out.println(student1);//打印通过反射获取到的字节码对象
        System.out.println(student2.getName());//获取全路径名,cn.tedu.reflection.Student
        System.out.println(student3.getSimpleName());//获取类名,Student
        System.out.println(student3.getPackage().getName());//获取包名,cn.tedu.reflection
    }

    /*单元测试2：获取构造方法*/
    @Test
    public void getConstruct(){
        //1.获取Student类的字节码对象
        Class<?> clazz = Student.class;
        //2.通过字节码对象获取所有的公开的构造方法
        Constructor<?>[] cs = clazz.getConstructors();
        //3.使用增强for循环遍历存放所有构造函数的数组
        //for(1 2 : 3){} 3 表示的是要遍历的构造函数数组 1表示当前遍历到的构造函数的类型 2参数名
        for(Constructor c: cs){
            //通过本轮循环遍历到的构造函数对象，获取构造函数名
            System.out.println(c.getName());
            //通过本轮循环遍历到的构造函数对象，获取这个构造函数的参数类型
            Class[] cp = c.getParameterTypes();
            System.out.println(Arrays.toString(cp));
        }
    }
    /*单元测试3：获取普通方法*/
    @Test
    public void getFunction() throws Exception {
        //获取字节码对象
        Class<?> clazz = Class.forName("cn.tedu.reflection.Srudent");
        //获取指定类中的所有普通方法
        Method[] ms = clazz.getMethods();
        //遍历存放所有方法的数组
        for (Method m : ms){
            //通过当前遍历到的方法对象获取当前方法的名字
            System.out.println(m.getName());
            //通过当前遍历到的方法对象获取当前方法的参数类型
            Class<?>[] pt = m.getParameterTypes();
            System.out.println(Arrays.toString(pt));
        }
    }

    /*单元测试方法4：获取成员变量*/
    @Test
    public void getFields(){
        /*注意！！！目前所有成员变量的修饰符必须是public才能获取到
        * 如果属性采用的是默认的修饰符，是反射不到的*/
        //1.获取Class字节码对象
        /* Class<?>中的“？”是泛型约束的通配符，类似于“*” */
        Class<?> clazz = Student.class;
        //2.通过字节码对象获取所有的成员变量
        Field[] fs = clazz.getFields();
        //3.遍历数组，获取每个成员变量的信息
        for(Field f : fs){
            System.out.println(f.getName());
            System.out.println(f.getType().getName());
        }
    }
    /*单元测试 5 ：通过单元测试来反射创建对象
    * 方式一：通过字节码对象直接调用newInstance(),触发无参构造来创建对象
    * 方式二：先获取指定的构造函数，再通过构造函数对象调用newInstance,触发对应类型的构造函数来创建对象
    * */
    @Test
    public void makeObject() throws Exception {
        //1.获取字节码对象class
        Class<?> clazz = Student.class;

        //2.创建对象
        Object obj = clazz.newInstance();//触发的是无参构造
        System.out.println(obj);//cn.tedu.reflection.Student@4ee285c6

        //3.通过全参构造创建对象
        //3.1 通过指定的构造函数来创建对象
        /*注意参数类型的指定，是对应类型的字节码对象，不能直接光写类型*/
        Constructor<?> c = clazz.getConstructor(String.class,int.class);
        //3.2触发刚刚获取到的全参构造
        Object obj2 = c.newInstance("张三",18);/*此处是多态的向上造型*/
        System.out.println(obj2);
        /*查看对象具体的属性值，或者调用方法，需要把Object强转成指定的子类对象
        * 为什么要把Object转成子类类型呢？因为想要使用子类的属性或者是特有功能
        * 父类是无法使用子类的特有属性和功能的，上面的obj2就是Object类型
        * Object类中没有Student中的属性和特有功能
        * 这种把之前看作是父类类型的子对象，重新转回成子类对象的现象，叫做向下造型
        * */
        Student s = (Student) obj2;
        System.out.println(s.name);
        System.out.println(s.age);
        s.study();
    }





}
